<script lang="ts" module>
    import { getContext, setContext } from "svelte";

    export const CascaderContextKey = Symbol("CascaderContextKey");
    export const useCascaderContext = () => getContext(CascaderContextKey);
</script>

<script lang="ts">
    import { Value } from "../Common/Value";
    import { Dropdown } from "../Dropdown";
    import { useClassList } from "../utils/useClassList";
    import type { CascaderProps, CascaderStoreProps } from "./Cascader";
    import { CascaderStore, type NodeKeyType } from "./store.svelte";
    import Option from "./Option.svelte";
    import Menu from "./Menu.svelte";
    import { useFormItemContext } from "../FormItem/FormItem.svelte";
    import { Empty } from "../Empty";

    let { visible = $bindable(false), value = $bindable([]), ...props }: CascaderProps = $props();
    const ctx = useFormItemContext();
    let query = $state("");
    const trigger = props.trigger ?? "click";
    const emptyText = props.emptyText ?? "暂无数据";
    let filterWrap: any;
    const allTextNodes: Node[] = [];
    const titleField = props.titleField ?? "title";
    const seperator = props.seperator ?? "/";
    const align = props.align ?? "bottomLeft";

    const store = $state({
        data: [],
        nodeMap: {},
        columns: [],
        filteredList: [],
    } as CascaderStoreProps);

    const cascaderStore = new CascaderStore(store, props);

    cascaderStore.onValueChange = (val: NodeKeyType[]) => {
        value = val;
        props.onchange?.(val);
        ctx?.onFieldChange?.();
    };

    $effect(() => {
        cascaderStore.value = value as NodeKeyType[];
    });

    // $effect(() => {
    //     store.data = props.data;
    //     init();
    // });

    const classList = $derived(
        useClassList(props, "cm-cascader", {
            "cm-cascader-disabled": props.disabled,
            "cm-cascader-clearable": !props.disabled && props.clearable && cascaderStore.value && cascaderStore.value.length,
            [`cm-cascader-${props.size}`]: props.size,
        }),
    );

    const text = $derived.by(() => {
        const vals = cascaderStore.value;
        const newVals = vals?.filter((val) => store.nodeMap[val]);
        const arr = newVals
            ? newVals.map((val: any) => {
                  const item = store.nodeMap[val];
                  return props.multi ? item : item[titleField];
              })
            : [];
        return props.multi ? arr : arr.length ? { title: arr.join(seperator) } : { title: "" };
    });

    const onSelect = (item: any) => {
        // 点击的是叶子节点或者设置点击即改变
        if (!props.multi) {
            if (!(item.children && item.children.length) || props.changeOnSelect) {
                props.onselect?.(item);
                const vals = cascaderStore.selectedKey;
                const rets = [...vals];
                cascaderStore.value = rets;
                if (props.filter) {
                    query = "";
                }
                value = vals;
                props.onchange?.(rets);
                ctx?.onFieldChange?.();
            }
        }
        // 点击叶子节点进行关闭
        if (!(item.children && item.children.length) && !props.multi) {
            visible = false;
        }
    };

    const onClear = () => {
        cascaderStore.value = [];
        value = [];
        props.onchange?.([]);
        ctx?.onFieldChange?.();
    };

    // 过滤查询
    $effect(() => {
        const queryStr = query;
        if (queryStr) {
            cascaderStore.filter(queryStr);

            // 高亮搜索字符
            queueMicrotask(() => {
                buildNodes();
                hilightKeyword(queryStr);
            });
        }
    });

    // 高亮关键字
    const hilightKeyword = (queryStr: string) => {
        // 不支持高亮则返回
        if (!CSS.highlights) {
            return;
        }

        CSS.highlights.delete("cm-search-results");

        const str = queryStr.trim().toLowerCase();
        if (!str) {
            return;
        }

        const ranges = allTextNodes
            .map((el) => {
                return { el, text: el.textContent?.toLowerCase() };
            })
            .map(({ text, el }) => {
                const indices = [];
                let startPos = 0;
                while (text && startPos < text.length) {
                    const index = text.indexOf(str, startPos);
                    if (index === -1) break;
                    indices.push(index);
                    startPos = index + str.length;
                }
                return indices.map((index) => {
                    const range = new Range();
                    range.setStart(el, index);
                    range.setEnd(el, index + str.length);
                    return range;
                });
            });

        const searchResultsHighlight = new Highlight(...ranges.flat());
        CSS.highlights.set("cm-search-results", searchResultsHighlight);
    };

    // 撤消按键，删除最后一个value
    const onDeleteLastValue = () => {
        if (props.multi) {
            const val = cascaderStore.value;
            if (val.length > 0) {
                const key = val.pop();
                cascaderStore.checkNode(key!, false);
            }
        }
    };

    const clearQuery = () => {
        query = "";
    };

    /**
     * 构建搜索的节点
     * @returns
     */
    const buildNodes = () => {
        // 不支持高亮则返回
        if (!CSS.highlights) {
            return;
        }

        const treeWalker = document.createTreeWalker(filterWrap, NodeFilter.SHOW_TEXT);
        let currentNode = treeWalker.nextNode();
        while (currentNode) {
            allTextNodes.push(currentNode);
            currentNode = treeWalker.nextNode();
        }
    };

    setContext(CascaderContextKey, {
        onSelect,
        loadData: props.loadData,
        multi: props.multi,
        clearQuery,
        getCascaderStore: () => cascaderStore,
    });
</script>

<div class={classList} tabIndex="0">
    <Dropdown bind:visible transfer={props.transfer} {align} revers={props.revers} trigger="click" disabled={props.disabled}>
        {#snippet menu()}
            <div class="cm-cascader-dropdown">
                {#if props.header}
                    {#if typeof props.header === "function"}
                        {@render props.header()}
                    {:else}
                        {props.header}
                    {/if}
                {/if}
                {#if query}
                    <div class="cm-cascader-wrap" bind:this={filterWrap}>
                        <div class={{ "cm-cascader-list": true, "cm-cascader-list-empty": !store.filteredList?.length }}>
                            {#if store.filteredList?.length}
                                {#each store.filteredList as item}
                                    <Option filter={props.filter} store={cascaderStore} data={item} {seperator} />
                                {/each}
                            {:else}
                                <div class="cm-cascader-empty">
                                    <Empty desc={emptyText} />
                                </div>
                            {/if}
                        </div>
                    </div>
                {:else}
                    <div class="cm-cascader-wrap">
                        {#each store.columns as column, index (index)}
                            <Menu
                                key={cascaderStore.selectedKey[index - 1] || "root"}
                                data={column}
                                {trigger}
                                titleField={cascaderStore.titleField}
                                {store}
                                level={index}
                                valueFile={cascaderStore.valueField}
                                emptyText={props.emptyText}
                            />
                        {/each}
                    </div>
                {/if}
                {#if props.footer}
                    {#if typeof props.footer === "function"}
                        {@render props.footer()}
                    {:else}
                        {props.footer}
                    {/if}
                {/if}
            </div>
        {/snippet}
        {#if props.triggerRender}
            <span class="cm-cascader-trigger">{@render props.triggerRender?.(text, cascaderStore.value)}</span>
        {:else}
            <Value
                prepend={props.prepend}
                {text}
                showMore={props.showMore}
                showMax={props.showMax}
                {onClear}
                clearable={props.clearable}
                placeholder={props.placeholder}
                disabled={props.disabled}
                size={props.size}
                multi={props.multi}
                bind:query
                filter={props.filter}
                {onDeleteLastValue}
                tagRender={props.tagRender}
            />
        {/if}
    </Dropdown>
</div>
